#region license
/*
 * Copyright (C) 2007 CchenSoft.com
 * 
 * This library is free software; you can redistribute it and/or modify it 
 * under the terms of the GNU Lesser General Public License 2.1 or later, as
 * published by the Free Software Foundation. See the included License.txt
 * or http://www.gnu.org/copyleft/lesser.html for details.
 * 
 * Author: Billy Zhang
 */
#endregion

using System.Collections;
using System.Xml;
using System;
using System.Collections.Generic;

namespace CchenSoft.Workflow.Loader
{
    public class StepDescriptor : AbstractDescriptor, IValidatable
    {
        //~ Instance fields ////////////////////////////////////////////////////////

        protected IList<ActionDescriptor> actions = new List<ActionDescriptor>();

        /**
         * this list maintained internally to allow for proper xml serialization.
         * All common-action elements in the XML file are expanded into ActionDescriptors
         * and are available via getActions()
         */
        protected List<int> commonActions = new List<int>();
        protected IList<PermissionDescriptor> permissions = new List<PermissionDescriptor>();
        protected IList<FunctionDescriptor> postFunctions = new List<FunctionDescriptor>();
        protected IList<FunctionDescriptor> preFunctions = new List<FunctionDescriptor>();
        protected IDictionary metaAttributes = new Hashtable();
        protected string name;
        protected bool hasActions = false;

        //~ Constructors ///////////////////////////////////////////////////////////

        /**
         * @deprecated use {@link DescriptorFactory} instead
         */
        public StepDescriptor()
        {
        }

        /**
         * @deprecated use {@link DescriptorFactory} instead
         */
        public StepDescriptor(XmlElement step)
        {
            Init(step);
        }

        /** sets parent */
        public StepDescriptor(XmlElement step, AbstractDescriptor parent)
        {
            Parent = parent;
            Init(step);
        }

        //~ Methods ////////////////////////////////////////////////////////////////

        public ActionDescriptor GetAction(int id)
        {
            for (IEnumerator iterator = actions.GetEnumerator(); iterator.MoveNext(); )
            {
                ActionDescriptor action = (ActionDescriptor)iterator.Current;

                if (action.Id == id)
                {
                    return action;
                }
            }

            return null;
        }

        /**
         * Get a List of {@link ActionDescriptor}s for this step
         */
        public IList<ActionDescriptor> Actions
        {
            get { return actions; }
        }

        /**
         * Get a list of common actions.
         * @return a List of Integer action id's.
         */
        public IList<int> CommonActions
        {
            get { return commonActions; }
        }

        public IDictionary MetaAttributes
        {
            get { return metaAttributes; }
            set { this.metaAttributes = value; }
        }

        public string Name
        {
            get { return name; }
            set { this.name = value; }
        }

        /**
         * Get a List of {@link PermissionDescriptor}s for this step
         */
        public IList<PermissionDescriptor> Permissions
        {
            get { return permissions; }
        }

        public IList<FunctionDescriptor> PostFunctions
        {
            set { postFunctions = value; }
            get { return postFunctions; }
        }

        public IList<FunctionDescriptor> PreFunctions
        {
            set { preFunctions = value; }
            get { return preFunctions; }
        }

        /**
         * Remove all common and regular actions for this step.
         */
        public void RemoveActions()
        {
            commonActions.Clear();
            actions.Clear();
            hasActions = false;
        }

        public bool ResultsInJoin(int join)
        {
            foreach (ActionDescriptor actionDescriptor in actions)
            {
                if (actionDescriptor.UnconditionalResult.Join == join)
                {
                    return true;
                }

                IList<ConditionalResultDescriptor> results = actionDescriptor.ConditionalResults;

                foreach (ConditionalResultDescriptor resultDescriptor in results)
                {
                    if (resultDescriptor.Join == join)
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        public void Validate()
        {
            if ((commonActions.Count == 0) && (actions.Count == 0) && hasActions)
            {
                throw new InvalidWorkflowDescriptorException("Step '" + name + "' actions element must contain at least one action or common-action");
            }

            if (Id == -1)
            {
                throw new InvalidWorkflowDescriptorException("Cannot use a step ID of -1 as it is a reserved value");
            }

            ValidationHelper.Validate((ICollection<IValidatable>)actions);
            ValidationHelper.Validate((ICollection<IValidatable>)permissions);
            ValidationHelper.Validate((ICollection<IValidatable>)preFunctions);
            ValidationHelper.Validate((ICollection<IValidatable>)postFunctions);

            foreach (int actionId in commonActions)
            {
                ActionDescriptor commonActionReference = (ActionDescriptor)((WorkflowDescriptor)Parent).CommonActions[actionId];

                if (commonActionReference == null)
                {
                    throw new InvalidWorkflowDescriptorException("Common action " + actionId + " specified in step " + name + " does not exist");
                }
            }
        }

        public override void WriteXML(XmlTextWriter writer, int indent)
        {
            XMLUtil.printIndent(writer, indent++);
            //writer.print("<step id=\"" + getId() + "\"");
            writer.WriteStartElement("step");
            writer.WriteAttributeString("id", Id.ToString());

            if ((name != null) && (name.Length > 0))
            {
                //writer.print(" name=\"" + XMLUtil.encode(name) + "\"");
                writer.WriteAttributeString("name", XMLUtil.Encode(name));
            }

            foreach (object key in metaAttributes.Keys)
            {
                XMLUtil.printIndent(writer, indent);
                writer.WriteStartElement("meta");
                writer.WriteAttributeString("name", key.ToString());
                writer.WriteString(metaAttributes[key].ToString());
                writer.WriteEndElement();
            }

            if (preFunctions.Count > 0)
            {
                XMLUtil.printIndent(writer, indent++);
                writer.WriteStartElement("pre-functions");

                for (int i = 0; i < preFunctions.Count; i++)
                {
                    FunctionDescriptor function = (FunctionDescriptor)preFunctions[i];
                    function.WriteXML(writer, indent);
                }

                XMLUtil.printIndent(writer, --indent);
                writer.WriteEndElement(); // ("</pre-functions>");
            }

            if (permissions.Count > 0)
            {
                XMLUtil.printIndent(writer, indent++);
                writer.WriteStartElement("external-permissions");

                for (int i = 0; i < permissions.Count; i++)
                {
                    PermissionDescriptor permission = (PermissionDescriptor)permissions[i];
                    permission.WriteXML(writer, indent);
                }

                XMLUtil.printIndent(writer, --indent);
                writer.WriteEndElement(); //("</external-permissions>");
            }

            if ((actions.Count > 0) || (commonActions.Count > 0))
            {
                XMLUtil.printIndent(writer, indent++);
                writer.WriteStartElement("actions");

                // special serialization common-action elements
                for (int i = 0; i < commonActions.Count; i++)
                {
                    writer.WriteStartElement("common-action");
                    writer.WriteAttributeString("id", commonActions[i].ToString());
                    writer.WriteEndElement();
                }

                for (int i = 0; i < actions.Count; i++)
                {
                    ActionDescriptor action = (ActionDescriptor)actions[i];

                    if (!action.IsCommon)
                    {
                        action.WriteXML(writer, indent);
                    }
                }

                XMLUtil.printIndent(writer, --indent);
                writer.WriteEndElement(); //("</actions>");
            }

            if (postFunctions.Count > 0)
            {
                XMLUtil.printIndent(writer, indent++);
                writer.WriteStartElement("post-functions");

                for (int i = 0; i < postFunctions.Count; i++)
                {
                    FunctionDescriptor function = (FunctionDescriptor)postFunctions[i];
                    function.WriteXML(writer, indent);
                }

                XMLUtil.printIndent(writer, --indent);
                writer.WriteEndElement(); //("</post-functions>");
            }

            XMLUtil.printIndent(writer, --indent);
            writer.WriteEndElement(); //("</step>");
        }

        protected void Init(XmlElement step)
        {
            string id = XMLUtil.GetAttributeValue(step, "id");
            try
            {
                Id = (Convert.ToInt32(id));
            }
            catch (Exception ex)
            {
                throw new ArgumentException("Invalid step id value " + id);
            }

            name = XMLUtil.GetAttributeValue(step, "name");

            XmlNodeList children = step.ChildNodes;

            for (int i = 0; i < children.Count; i++)
            {
                XmlNode child = children[i];

                if (child.Name.Equals("meta"))
                {
                    XmlElement meta = (XmlElement)child;
                    this.metaAttributes.Add(XMLUtil.GetAttributeValue(meta, "name"), meta.InnerText);
                }
            }

            // set up pre-functions - OPTIONAL
            XmlNode pre = step.SelectSingleNode("pre-functions");

            if (pre != null)
            {
                XmlNodeList preFunctions = pre.SelectNodes("function");

                for (int k = 0; k < preFunctions.Count; k++)
                {
                    XmlElement preFunction = (XmlElement)preFunctions[k];
                    FunctionDescriptor functionDescriptor = DescriptorFactory.GetFactory().CreateFunctionDescriptor(preFunction);
                    functionDescriptor.Parent = this;
                    this.preFunctions.Add(functionDescriptor);
                }
            }

            // set up permissions - OPTIONAL
            XmlNode p = step.SelectSingleNode("external-permissions");

            if (p != null)
            {
                XmlNodeList permissions = p.SelectNodes("permission");

                for (int i = 0; i < permissions.Count; i++)
                {
                    XmlElement permission = (XmlElement)permissions[i];
                    PermissionDescriptor permissionDescriptor = DescriptorFactory.GetFactory().CreatePermissionDescriptor(permission);
                    permissionDescriptor.Parent = this;
                    this.permissions.Add(permissionDescriptor);
                }
            }

            // set up actions - OPTIONAL
            XmlNode a = step.SelectSingleNode("actions");

            if (a != null)
            {
                hasActions = true;

                XmlNodeList actions = a.SelectNodes("action");

                for (int i = 0; i < actions.Count; i++)
                {
                    XmlElement action = (XmlElement)actions[i];
                    ActionDescriptor actionDescriptor = DescriptorFactory.GetFactory().CreateActionDescriptor(action);
                    actionDescriptor.Parent = this;
                    this.actions.Add(actionDescriptor);
                }

                // look for common-action elements
                XmlNodeList commonActions = a.SelectNodes("common-action");

                for (int i = 0; i < commonActions.Count; i++)
                {
                    XmlElement commonAction = (XmlElement)commonActions[i];

                    WorkflowDescriptor workflowDescriptor = (WorkflowDescriptor)Parent;

                    try
                    {
                        int actionId = Convert.ToInt32(XMLUtil.GetAttributeValue(commonAction, "id"));

                        ActionDescriptor commonActionReference = (ActionDescriptor)workflowDescriptor.CommonActions[actionId];

                        if (commonActionReference != null)
                        {
                            this.actions.Add(commonActionReference);
                        }

                        this.commonActions.Add(actionId);
                    }
                    catch (Exception ex)
                    {
                        //log.warn("Invalid common actionId:" + ex);
                    }
                }
            }

            // set up post-functions - OPTIONAL
            XmlNode post = step.SelectSingleNode("post-functions");

            if (post != null)
            {
                XmlNodeList postFunctions = post.SelectNodes("function");

                for (int k = 0; k < postFunctions.Count; k++)
                {
                    XmlElement postFunction = (XmlElement)postFunctions[k];
                    FunctionDescriptor functionDescriptor = DescriptorFactory.GetFactory().CreateFunctionDescriptor(postFunction);
                    functionDescriptor.Parent = this;
                    this.postFunctions.Add(functionDescriptor);
                }
            }
        }
    }
}
